package org.obm.mina;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class MinaServer<T> implements Server<T> {

	private static final Logger logger = LoggerFactory
			.getLogger(MinaServer.class);

	protected final SocketAcceptor acceptor;
	protected final T serverConfig;

	/**
	 * Creates a new server for the specified configuration.
	 * 
	 * @param config
	 *            the ServerConfig for the server
	 * @param pool
	 *            the optional handler thread pool to use for this server
	 * @throws IOException
	 *             if an I/O error occurred while creating the server
	 * @throws ServiceException
	 *             if a ServiceException occured
	 */
	protected MinaServer(T config) throws IOException,
			ServiceException {
		this.serverConfig = config;
		acceptor = new NioSocketAcceptor(Runtime.getRuntime()
				.availableProcessors());
		logger.info("MINA server created");
	}

	/**
	 * Returns the configuration for this server.
	 * @return 
	 * 
	 * @return the ServerConfig for this server
	 */
	public  T getConfig() {
		return serverConfig;
	}

	/**
	 * Starts the server. Binds the server port and starts the connection
	 * handler. Optionally adds an SSLFilter if ssl is enabled.
	 * 
	 * @throws IOException
	 *             if an I/O error occured while starting the server
	 */
	public void start() throws IOException {
		try {
			IoHandler handler = new MinaIoHandler<T>(this);
			acceptor.setHandler(handler);
			DefaultIoFilterChainBuilder fc = acceptor.getFilterChain();
			acceptor.setReuseAddress(true);
			fc.addLast("codec", new ProtocolCodecFilter(
					getProtocolCodecFactory()));
			acceptor.bind(new InetSocketAddress(2400));
			getLog().info(
					"Starting listener. active: " + acceptor.isActive()
							+ " h: " + acceptor.getHandler());
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

	/**
	 * Shuts down the server. Waits up to 'graceSecs' seconds for the server to
	 * stop, otherwise the server is forced to shut down.
	 * 
	 * @param graceSecs
	 *            number of seconds to wait before forced shutdown
	 */
	public void shutdown(int graceSecs) {
		getLog().info("Initiating shutdown");
		// Would prefer to unbind first then cleanly close active connections,
		// but mina unbind seems to automatically close the active sessions
		// so we must close connections then unbind, which does expose us to
		// a potential race condition.
		long graceMSecs = graceSecs * 1000;
		// Close active sessions and handlers
		closeSessions(graceMSecs);
		// Unbind listener socket
		acceptor.unbind();
	}

	private void closeSessions(long timeout) {
		// Close currently open sessions and get active handlers
		List<MinaHandler> handlers = new ArrayList<MinaHandler>();
		for (IoSession session : getSessions()) {
			getLog().info("Closing session = " + session);
			MinaHandler handler = MinaIoHandler.getMinaHandler(session);
			if (handler != null)
				handlers.add(handler);
		}
		// Wait up to grace seconds to close active handlers
		long start = System.currentTimeMillis();
		for (MinaHandler handler : handlers) {
			long elapsed = System.currentTimeMillis() - start;
			try {
				// Force close if timeout - elapsed < 0
				handler.dropConnection(timeout - elapsed);
			} catch (IOException e) {
				getLog().warn("Error closing handler: %s", handler, e);
			}
		}
	}

	public Collection<IoSession> getSessions() {
		return acceptor.getManagedSessions().values();
	}

	/**
	 * Creates a new handler for handling requests received by the specified
	 * MINA session (connection).
	 * 
	 * @param session
	 *            the I/O session (connection) being opened
	 * @return the MinaHandler for handling requests
	 */
	protected abstract MinaHandler createHandler(IoSession session);

	protected abstract ProtocolCodecFactory getProtocolCodecFactory();

	/**
	 * Returns the logger for server log messages.
	 * 
	 * @return the Logger for this server
	 */
	public abstract Logger getLog();
}
